// (C) 2022 Intel Corporation. All rights reserved.
// Your use of Intel Corporation's design tools, logic functions and other 
// software and tools, and its AMPP partner logic functions, and any output 
// files from any of the foregoing (including device programming or simulation 
// files), and any associated documentation or information are expressly subject 
// to the terms and conditions of the Intel Program License Subscription 
// Agreement, Intel FPGA IP License Agreement, or other applicable 
// license agreement, including, without limitation, that your use is for the 
// sole purpose of programming logic devices manufactured by Intel and sold by 
// Intel or its authorized distributors.  Please refer to the applicable 
// agreement for further details.

`define I2C_MASTER_MAX_ADDR     15 // define how many DW registers are used
`define I2C_SLV_ADDR            8'haa //1010_1010
//Define Address for each regiter defined in Altera i2c_mst ip
`define TFR_CMD_ADDR            4'h0
`define RX_DATA_ADDR            4'h1
`define CTRL_ADDR               4'h2
`define ISER_ADDR               4'h3
`define ISR_ADDR                4'h4
`define STATUS_ADDR             4'h5
`define TFR_CMD_FIFO_LVL_ADDR   4'h6
`define RX_DATA_FIFO_LVL_ADDR   4'h7
`define SCL_LOW_ADDR            4'h8
`define SCL_HIGH_ADDR           4'h9
`define SDA_HOLD_ADDR           4'hA


module i2c_mst_wrapper 
    #(
        parameter TIMEOUT_PARAM = 2500
    )
    
(
    input             clk,
    input             reset,
    //avmm connected to MUX select
    input      [31:0] avmm_address,
    input             avmm_write,
    input      [31:0] avmm_writedata,          
    input             avmm_read,     
    output reg [31:0] avmm_readdata,      
    output reg        avmm_readdatavalid, 
    output            avmm_waitrequest,           
    input      [ 3:0] avmm_byteenable,
    //i2c interface to debug CPLD
    input             i2c_debug_data_in,
    input             i2c_debug_clk_in, 
    output            i2c_debug_data_oe,
    output            i2c_debug_clk_oe,
	 //IIC_MM access error report
	 output            invalid_access_dbg
);

    localparam START              = 8'h00; //reset to START state. Disable Avalon I2C (Master) core through CTRL register
    localparam INI_CFG_ISER       = 8'h01;
    localparam INI_SCL_LOW        = 8'h02; //Configure ISER, SCL_LOW, SCL_HIGH, SDA_HOLD register
    localparam INI_SCL_HIGH       = 8'h03; //Configure ISER, SCL_LOW, SCL_HIGH, SDA_HOLD register
    localparam INI_SDA_HOLD       = 8'h04; //Configure ISER, SCL_LOW, SCL_HIGH, SDA_HOLD register
    localparam INI_ENABLE_CORE    = 8'h05; //Configure ISER, SCL_LOW, SCL_HIGH, SDA_HOLD register
    localparam BUS_IDLE           = 8'h06; //Configure control bits and enable the Avalon I2C (Master) core through CTRL register
    localparam WR_TFR_DEVICE      = 8'h08;
    localparam WR_TFR_ADD0        = 8'h09;
    localparam WR_TFR_ADD1        = 8'h0a;
    localparam WR_TFR_D0          = 8'h0b;
    localparam WR_TFR_D1          = 8'h0c;
    localparam WR_TFR_D2          = 8'h0d;
    localparam WR_TFR_D3          = 8'h0e;
    localparam WR_WAIT            = 8'h0f;//clear addr_store and data_store before returning back to BUS_IDLE
    localparam RDW_TFR_DEVICE     = 8'h10;
    localparam RDW_TFR_ADD0       = 8'h11;
    localparam RDW_TFR_ADD1       = 8'h12;
    localparam RD_TFR_DEVICE      = 8'h13;
    localparam RD_TFR_D0          = 8'h14;
    localparam RD_TFR_D1          = 8'h15;
    localparam RD_TFR_D2          = 8'h16;
    localparam RD_TFR_D3          = 8'h17;
    localparam RD_RX_FIFO_LVL     = 8'h18; //read Rx_FIFO LVL until 4 bytes received.
    localparam RX_FIFO_LVL_LATCH  = 8'h19; //wait until 4-byte read data recived                
    localparam RX_D0              = 8'h1a;
    localparam RX_D1              = 8'h1b;
    localparam RX_D2              = 8'h1c;
    localparam RX_D3              = 8'h1d;
    localparam ASSERT_RD_VALID    = 8'h1e;
    localparam RD_WAIT            = 8'h1f;
    localparam STORE_D1           = 8'h20;
    localparam STORE_D2           = 8'h21;
    localparam STORE_D3           = 8'h22;
    

//Internal Wires
reg  [15:0] addr_store;
reg  [31:0] writedata_store;
reg  [7 :0] readdata_store_0;
reg  [7 :0] readdata_store_1;
reg  [7 :0] readdata_store_2;
reg  [7 :0] readdata_store_3;

//AVMM interface connect to Altera I2C Masetr IP
reg  [ 3: 0] i2c_mst_address;
reg          i2c_mst_read;     
reg          i2c_mst_write;    
reg  [31: 0] i2c_mst_writedata;
wire [31: 0] i2c_mst_readdata; 

//error report
reg         avmm_waitrequest_ff;                     //one clock delay of read/write command

reg  [11:0] scl_stuck_counter;
reg  [16:0] transaction_counter;
   
wire        transaction_300us_done;                 

//Internal Regs
reg  [31:0] i2c_master_reg [`I2C_MASTER_MAX_ADDR:0]; //local register
reg  [31:0] illegal_write;
reg  [ 7:0] state;


assign      avmm_waitrequest       =   (avmm_write || avmm_read) || avmm_waitrequest_ff;   // to make sure waitrequest is asserted at the same time of read/write command

assign      invalid_access_dbg     =   (scl_stuck_counter == 12'd2000);                    //SCL keep LOW for over 100us;

assign      transaction_300us_done =   (transaction_counter == 16'd6000);                  //IIC transaction for 4 bytes read/write from master to slave will no longer than 300us 


always @ (posedge clk) begin
    if (reset) begin
        avmm_waitrequest_ff   <= 1'b0; 
    end 
	 else if (avmm_read || avmm_write) begin
        avmm_waitrequest_ff   <= 1'b1; 
    end
    else if (invalid_access_dbg) begin
        avmm_waitrequest_ff   <= 1'b0;
    end
	 else if (avmm_readdatavalid) begin
	     avmm_waitrequest_ff   <= 1'b0;
	 end
    else if (transaction_300us_done) begin
        avmm_waitrequest_ff   <= 1'b0;
    end        
end

always @ (posedge clk) begin
    if (reset) begin
        scl_stuck_counter      <= 12'd0;
    end 
	 else if (avmm_read || avmm_write) begin
	     scl_stuck_counter      <= 12'd0; 
	 end
	 else if (scl_stuck_counter < 12'd2000) begin
	     if (!i2c_debug_clk_in) begin
		      scl_stuck_counter  <=  scl_stuck_counter + 1'b1;
		  end
		  else begin
		      scl_stuck_counter  <= 12'd0;
		  end
	 end
	 else begin
	     scl_stuck_counter      <= 12'd0;
	 end
end

always @ (posedge clk) begin
    if (reset) begin
        transaction_counter    <= 16'd0; 
    end 
	 else if (avmm_read || avmm_write) begin
	     transaction_counter    <= 16'd0;
	 end
	else if (avmm_waitrequest && transaction_counter < 16'd6000) begin
	     transaction_counter    <= transaction_counter+ 1'b1;
	 end
	 else begin
	     transaction_counter    <= 16'd0;
	 end
end

always @ (posedge clk) begin
    if (reset) begin
        i2c_mst_address    <= 4'h0;
        i2c_mst_read       <= 1'b0;
        i2c_mst_write      <= 1'b0;
        i2c_mst_writedata  <= 32'h0;
        addr_store         <= 'h0;
        writedata_store    <= 'h0;
        readdata_store_0   <= 'h0;
        readdata_store_1   <= 'h0;
        readdata_store_2   <= 'h0;
        readdata_store_3   <= 'h0;
        avmm_readdatavalid <= 1'b0;
        avmm_readdata      <= 'b0;
        state              <= START;
    end else begin
        case (state)
            START: begin //Disable Avalon I2C (Master) core through CTRL register
               i2c_mst_address    <= `CTRL_ADDR;
               i2c_mst_write      <= 1'b1;
               i2c_mst_writedata  <= 32'h0;
               state              <= INI_CFG_ISER;
            end
            
            INI_CFG_ISER: begin // Enable 'Interrupt Status Enable Register'
               i2c_mst_address    <= `ISER_ADDR;
               i2c_mst_write      <= 1'b1;
               i2c_mst_writedata  <= 32'hFF;  
               state              <= INI_SCL_LOW;               
            end

            INI_SCL_LOW: begin
               i2c_mst_address    <= `SCL_LOW_ADDR;
               i2c_mst_write      <= 1'b1;
               i2c_mst_writedata  <= 32'd25;  
               state              <= INI_SCL_HIGH; 
            end
            
            INI_SCL_HIGH: begin
               i2c_mst_address    <= `SCL_HIGH_ADDR;
               i2c_mst_write      <= 1'b1;
               i2c_mst_writedata  <= 32'd25;
               state              <= INI_SDA_HOLD; 
            end
            //3 * clk_mhz (20M reference clock) / 10 = 6
            INI_SDA_HOLD: begin
               i2c_mst_address    <= `SDA_HOLD_ADDR;
               i2c_mst_write      <= 1'b1;
               i2c_mst_writedata  <= 32'd6;
               state              <= INI_ENABLE_CORE;
            end
            //write 0x3F to CTRL
            INI_ENABLE_CORE: begin
               i2c_mst_address    <= `CTRL_ADDR;
               i2c_mst_write      <= 1'b1;
               i2c_mst_writedata  <= 32'h3F;
               state              <= BUS_IDLE;            
            end
            
            BUS_IDLE: begin //Init done, ready for avmm commands
               i2c_mst_address    <= 'h0;
               i2c_mst_write      <= 'h0;
               i2c_mst_writedata  <= 'h0;            
                if (invalid_access_dbg) begin
					     state           <= START;
					 end
					 else if ( avmm_write ) begin
                    addr_store      <= avmm_address[15:0];
                    writedata_store <= avmm_writedata;
                    state           <= WR_TFR_DEVICE; 
                end else if ( avmm_read ) begin
                    addr_store      <= avmm_address[15:0];
                    state           <= RDW_TFR_DEVICE;
                end else begin
                    state           <= BUS_IDLE;
                end
            end

            WR_TFR_DEVICE: begin
                i2c_mst_address    <= `TFR_CMD_ADDR;
                i2c_mst_write      <= 1'b1;
                i2c_mst_writedata  <= {22'h0, 1'b1, 1'b0, 7'b010_1010, 1'b0};
                state              <= WR_TFR_ADD0;
            end
            
            WR_TFR_ADD0: begin
                i2c_mst_address    <= `TFR_CMD_ADDR;
                i2c_mst_write      <= 1'b1;
                i2c_mst_writedata  <= {22'h0, 1'b0, 1'b0, addr_store[15:8]};
                state              <= WR_TFR_ADD1;            
            end
            
            WR_TFR_ADD1: begin
                i2c_mst_address    <= `TFR_CMD_ADDR;
                i2c_mst_write      <= 1'b1;
                i2c_mst_writedata  <= {22'h0, 1'b0, 1'b0, addr_store[7:0]};
                state              <= WR_TFR_D0;             
            end
            
            WR_TFR_D0: begin
                i2c_mst_address    <= `TFR_CMD_ADDR;
                i2c_mst_write      <= 1'b1;
                i2c_mst_writedata  <= {22'h0, 1'b0, 1'b0, writedata_store[7:0]};
                state              <= WR_TFR_D1;             
            end
            
            WR_TFR_D1: begin
                i2c_mst_address    <= `TFR_CMD_ADDR;
                i2c_mst_write      <= 1'b1;
                i2c_mst_writedata  <= {22'h0, 1'b0, 1'b0, writedata_store[15:8]};
                state              <= WR_TFR_D2;              
            end
            
            WR_TFR_D2: begin
                i2c_mst_address    <= `TFR_CMD_ADDR;
                i2c_mst_write      <= 1'b1;
                i2c_mst_writedata  <= {22'h0, 1'b0, 1'b0, writedata_store[23:16]};
                state              <= WR_TFR_D3;              
            end
            
            WR_TFR_D3: begin
                i2c_mst_address    <= `TFR_CMD_ADDR;
                i2c_mst_write      <= 1'b1;
                i2c_mst_writedata  <= {22'h0, 1'b0, 1'b1, writedata_store[31:24]};
                state              <= WR_WAIT;              
            end
            
            WR_WAIT: begin
                i2c_mst_address    <= 'h0;
                i2c_mst_write      <= 'h0;
                i2c_mst_writedata  <= 'h0;  
                addr_store         <= 'h0;
                writedata_store    <= 'h0; 
                state              <= BUS_IDLE;
            end
            //Start Read commands

            RDW_TFR_DEVICE: begin
                i2c_mst_address    <= `TFR_CMD_ADDR;
                i2c_mst_write      <= 1'b1;
                i2c_mst_writedata  <= {22'h0, 1'b1, 1'b0, 7'b010_1010, 1'b0};
                state              <= RDW_TFR_ADD0;             
            end
            
            RDW_TFR_ADD0: begin
                i2c_mst_address    <= `TFR_CMD_ADDR;
                i2c_mst_write      <= 1'b1;
                i2c_mst_writedata  <= {22'h0, 1'b0, 1'b0, addr_store[15:8]};
                state              <= RDW_TFR_ADD1;               
            end
            
            RDW_TFR_ADD1: begin
                i2c_mst_address    <= `TFR_CMD_ADDR;
                i2c_mst_write      <= 1'b1;
                i2c_mst_writedata  <= {22'h0, 1'b0, 1'b0, addr_store[7:0]};
                state              <= RD_TFR_DEVICE;            
            end
            //restart
            RD_TFR_DEVICE: begin
                i2c_mst_address    <= `TFR_CMD_ADDR;
                i2c_mst_write      <= 1'b1;
                i2c_mst_writedata  <= {22'h0, 1'b1, 1'b0, 7'b010_1010, 1'b1};
                state              <= RD_TFR_D0;             
            end
            //Ack byte of data
            RD_TFR_D0: begin
                i2c_mst_address    <= `TFR_CMD_ADDR;
                i2c_mst_write      <= 1'b1;
                i2c_mst_writedata  <= {22'h0, 1'b0, 1'b0, 8'b0};
                state              <= RD_TFR_D1;             
            end
            
            RD_TFR_D1: begin
                i2c_mst_address    <= `TFR_CMD_ADDR;
                i2c_mst_write      <= 1'b1;
                i2c_mst_writedata  <= {22'h0, 1'b0, 1'b0, 8'b0};
                state              <= RD_TFR_D2;             
            end
            
            RD_TFR_D2: begin
                i2c_mst_address    <= `TFR_CMD_ADDR;
                i2c_mst_write      <= 1'b1;
                i2c_mst_writedata  <= {22'h0, 1'b0, 1'b0, 8'b0};
                state              <= RD_TFR_D3;             
            end
            //Ack last byte of data
            RD_TFR_D3: begin
                i2c_mst_address    <= `TFR_CMD_ADDR;
                i2c_mst_write      <= 1'b1;
                i2c_mst_writedata  <= {22'h0, 1'b0, 1'b1, 8'b0};
                state              <= RD_RX_FIFO_LVL;
            end
            
            RD_RX_FIFO_LVL: begin
                i2c_mst_read       <= 1'b1;
                i2c_mst_address    <= `RX_DATA_FIFO_LVL_ADDR;
                i2c_mst_write      <= 1'b0;
                i2c_mst_writedata  <= 'b0;
					 if(invalid_access_dbg) begin
						 state           <= START;
				    end
					 else begin
					    state           <= RX_FIFO_LVL_LATCH;
				    end	                          
            end
            //When 4 bytes of data received, start to construct readdata to MUX.
            RX_FIFO_LVL_LATCH: begin
                i2c_mst_read       <= 1'b0;
					 if(invalid_access_dbg) begin
						 state           <= START;
				    end 
                else if ( i2c_mst_readdata == 32'h4 ) begin
                    state          <= RX_D0;
                end 
					 else begin
                    state          <= RD_RX_FIFO_LVL;
                end
            end
            //start to construct readdata to master
            RX_D0: begin
                i2c_mst_read       <= 1'b1;
                i2c_mst_address    <= `RX_DATA_ADDR;
                state              <= RX_D1;
            end
            
            RX_D1: begin
                state              <= RX_D2;
            end
            
            RX_D2: begin
                state              <= RX_D3;
            end  
            //(STORE_D0)readdata start to show up from here. Store second byte of readata
            RX_D3: begin
                readdata_store_0   <= i2c_mst_readdata[7:0];
                state              <= STORE_D1;
            end
            //Store second byte of readata
            STORE_D1: begin
                i2c_mst_read       <= 1'b0;
                i2c_mst_address    <= 'b0; 
                readdata_store_1   <= i2c_mst_readdata[7:0];
                state              <= STORE_D2;
            end
            //Store third byte of readata
            STORE_D2: begin
                readdata_store_2   <= i2c_mst_readdata[7:0];
                state              <= STORE_D3;
            end
            //Store forth byte of readata
            STORE_D3: begin
                readdata_store_3   <= i2c_mst_readdata[7:0];
                state              <= ASSERT_RD_VALID;
            end
            //returm readdata
            ASSERT_RD_VALID: begin
               
                avmm_readdatavalid <= 1'b1;
                avmm_readdata      <= { readdata_store_3, readdata_store_2, readdata_store_1, readdata_store_0 };
                state              <= RD_WAIT;
            end   

            RD_WAIT: begin             
                avmm_readdatavalid <= 1'b0;
                avmm_readdata      <= 'b0;
                readdata_store_0   <= 'h0;
                readdata_store_1   <= 'h0;
                readdata_store_2   <= 'h0;
                readdata_store_3   <= 'h0;
                state              <= BUS_IDLE;
            end            
            
            default: begin
                state <= START;
            end
        endcase
    end
end


//I2C Master translates AVMM to I2C and bridge memory map access to Debug CPLD.
i2c_mst i2c_mst_inst 
(
	.i2c_0_clock_clk            ( clk                 ),            
	.i2c_0_reset_sink_reset_n   ( ~reset && ~invalid_access_dbg ), //if detect invalid_access from debug FPGA, force IIC_Master into reset mode 
    //i2c_mst_avmm intf
	.i2c_0_interrupt_sender_irq (                     ), //ouput, interrupt is not used
	.i2c_0_csr_address          ( i2c_mst_address     ), //[3:0]        
	.i2c_0_csr_read             ( i2c_mst_read        ),             
	.i2c_0_csr_write            ( i2c_mst_write       ),            
	.i2c_0_csr_writedata        ( i2c_mst_writedata   ), //[31:0]      
	.i2c_0_csr_readdata         ( i2c_mst_readdata    ), //[31:0]       
    //i2c master i2c signals
	.i2c_0_i2c_serial_sda_in    ( i2c_debug_data_in   ),    
	.i2c_0_i2c_serial_scl_in    ( i2c_debug_clk_in    ),    
	.i2c_0_i2c_serial_sda_oe    ( i2c_debug_data_oe   ),    
	.i2c_0_i2c_serial_scl_oe    ( i2c_debug_clk_oe    )     
);


// Debug visibility
// synthesis translate_off
reg  [8*20:1] state_ascii;
    always @(*)
    begin
        if (reset) begin
            state_ascii <= "START";
        end else begin  
            case (state)
                    START             : state_ascii <= " START             ";
                    INI_CFG_ISER      : state_ascii <= " INI_CFG_ISER      ";
                    INI_SCL_LOW       : state_ascii <= " INI_SCL_LOW       ";
                    INI_SCL_HIGH      : state_ascii <= " INI_SCL_HIGH      ";
                    INI_SDA_HOLD      : state_ascii <= " INI_SDA_HOLD      ";
                    INI_ENABLE_CORE   : state_ascii <= " INI_ENABLE_CORE   ";
                    BUS_IDLE          : state_ascii <= " BUS_IDLE          ";
                    WR_TFR_DEVICE     : state_ascii <= " WR_TFR_DEVICE     ";
                    WR_TFR_ADD0       : state_ascii <= " WR_TFR_ADD0       ";
                    WR_TFR_ADD1       : state_ascii <= " WR_TFR_ADD1       ";
                    WR_TFR_D0         : state_ascii <= " WR_TFR_D0         ";
                    WR_TFR_D1         : state_ascii <= " WR_TFR_D1         ";
                    WR_TFR_D2         : state_ascii <= " WR_TFR_D2         ";
                    WR_TFR_D3         : state_ascii <= " WR_TFR_D3         ";
                    WR_WAIT           : state_ascii <= " WR_WAIT           ";
                    RDW_TFR_DEVICE    : state_ascii <= " RDW_TFR_DEVICE    ";
                    RDW_TFR_ADD0      : state_ascii <= " RDW_TFR_ADD0      ";
                    RDW_TFR_ADD1      : state_ascii <= " RDW_TFR_ADD1      ";
                    RD_TFR_DEVICE     : state_ascii <= " RD_TFR_DEVICE     ";
                    RD_TFR_D0         : state_ascii <= " RD_TFR_D0         ";
                    RD_TFR_D1         : state_ascii <= " RD_TFR_D1         ";
                    RD_TFR_D2         : state_ascii <= " RD_TFR_D2         ";
                    RD_TFR_D3         : state_ascii <= " RD_TFR_D3         ";
                    RD_RX_FIFO_LVL    : state_ascii <= " RD_RX_FIFO_LVL    ";
                    RX_FIFO_LVL_LATCH : state_ascii <= " RX_FIFO_LVL_LATCH ";
                    RX_D0             : state_ascii <= " RX_D0             ";
                    RX_D1             : state_ascii <= " RX_D1             ";
                    RX_D2             : state_ascii <= " RX_D2             ";
                    RX_D3             : state_ascii <= " RX_D3             ";
                    ASSERT_RD_VALID   : state_ascii <= " ASSERT_RD_VALID   ";
                    RD_WAIT           : state_ascii <= " RD_WAIT           ";
                    STORE_D1          : state_ascii <= "STORE_D1           ";
                    STORE_D2          : state_ascii <= "STORE_D2           ";
                    STORE_D3          : state_ascii <= "STORE_D3           ";           
                    default           : state_ascii <= "ERROR              ";
            endcase
        end //else
    end //always
// synthesis translate_on

endmodule    